package com.hzqc.wxaapp.configure;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.data.redis.serializer.StringRedisSerializer;

import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * The type Spring redis cache configuration.
 *
 * @author n1
 * @since 2021 /4/12 16:57
 */
@EnableCaching
@Configuration(proxyBeanMethods = false)
public class SpringRedisCacheConfiguration {

    /**
     * Redis template.
     *
     * @param redisConnectionFactory the redis connection factory
     * @return the redis template
     */
    @Bean
    @Primary
    public RedisTemplate<Object, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
        RedisTemplate<Object, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory);
        // 使用Jackson2JsonRedisSerialize 替换默认序列化
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = initJacksonSerializer();
        // 设置value的序列化规则和 key的序列化规则
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer);
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }

    /**
     * Cache manager cache manager.
     *
     * @param redisConnectionFactory the redis connection factory
     * @return the cache manager
     */
    @Bean
    public CacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {

        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                // 默认策略，未配置的 key 会使用这个
                this.getRedisCacheConfigurationWithTtl(600),
                // 指定 key 策略
                this.getRedisCacheConfigurationMap()
        );
    }


    /**
     * Gets redis cache configuration map.
     *
     * @return the redis cache configuration map
     * @see CacheNameEnum
     */
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Class<CacheNameEnum> cacheNameEnumClass = CacheNameEnum.class;
        final Enum<?>[] enums = cacheNameEnumClass.getEnumConstants();
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>(100);
        for (Enum<?> e : enums) {
            CacheNameEnum cacheNameEnum = (CacheNameEnum) e;
            redisCacheConfigurationMap.put(cacheNameEnum.cacheName(), this.getRedisCacheConfigurationWithTtl(cacheNameEnum.ttlSecond()));
        }
        return redisCacheConfigurationMap;
    }

    /**
     * Gets redis cache configuration with ttl.
     *
     * @param seconds the seconds
     * @return the redis cache configuration with ttl
     */
    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = initJacksonSerializer();
        RedisSerializationContext.SerializationPair<Object> objectSerializationPair = RedisSerializationContext
                .SerializationPair
                .fromSerializer(jackson2JsonRedisSerializer);

        return RedisCacheConfiguration.defaultCacheConfig()
                .serializeValuesWith(objectSerializationPair)
                .entryTtl(Duration.ofSeconds(seconds));
    }


    /**
     * The enum Cache name enum.
     */
    public enum CacheNameEnum {

        /**
         * 用户jwt token 缓存空间 ttl 7天  保持与配置文件一致同时调整
         */
        JWT_TOKEN_CACHE("usrTkn", 7 * 24 * 60 * 60),
        /**
         * 小程序token 缓存 7200  设置7000提前刷新
         */
        WXA_TOKEN_CACHE("wxa::token",7000),
        /**
         * 验证码缓存 5分钟ttl
         */
        SMS_CAPTCHA_CACHE("smsCode", 5 * 60),
        /**
         * sessionKey缓存时间 1天
         */
        SESSION_KEY_CACHE("sky", 7 * 24 * 60 * 60),
        /**
         * urlScheme
         */
        WXA_SCHEME_CACHE("wxaUrl", 25 * 24 * 60 * 60);
        /**
         * 缓存名称
         */
        private final String cacheName;
        /**
         * 缓存过期秒数
         */
        private final int ttlSecond;

        CacheNameEnum(String cacheName, int ttlSecond) {
            this.cacheName = cacheName;
            this.ttlSecond = ttlSecond;
        }


        /**
         * Cache name string.
         *
         * @return the string
         */
        public String cacheName() {
            return this.cacheName;
        }


        /**
         * Ttl second int.
         *
         * @return the int
         */
        public int ttlSecond() {
            return this.ttlSecond;
        }
    }

    private Jackson2JsonRedisSerializer<Object> initJacksonSerializer() {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(om.getPolymorphicTypeValidator(), ObjectMapper.DefaultTyping.NON_FINAL);
        //bugFix Jackson2反序列化数据处理LocalDateTime类型时出错
        om.disable(SerializationFeature.WRITE_DATE_KEYS_AS_TIMESTAMPS);
        om.registerModule(new JavaTimeModule());
        jackson2JsonRedisSerializer.setObjectMapper(om);
        return jackson2JsonRedisSerializer;
    }
}
